/* 
 * File:   server_FileService.cpp
 * Author: Maximiliano Monzon (82341)
 * 
 * Created on October 22, 2012, 4:58 AM
 */
#include <iomanip>
#include <string.h>
#include <sstream>
#include <fstream>
#include <vector>
#include <arpa/inet.h>
#include <cmath>
#include <stdint.h>
#include <stdlib.h>
#include <cstdio>
#include "server_FileService.h"
#include "common_ProtocoloConstantes.h"
#include "common_Codec.h"
#include "server_Proceso.h"
#include "common_MutexScopeLocker.h"
#include "common_Repositorio.h"
#include "common_RepositorioBuilder.h"
#include "common_NumeroUtils.h"

using namespace std;
using namespace TP;
using namespace TP::Servicios;
using namespace TP::Archivos;

#define COMANDO_INVALIDO 0
#define IP_INVALIDA 0

FileService::FileService(IAdministradorRepositorioCentral *pAdministradorRepositorioCentral) : Service(FileServiceConstants::NOMBRE_SERVICIO) {
    
    _admRepositorioCentral = pAdministradorRepositorioCentral;
}
        
FileService::~FileService() {}

void FileService::Iniciar() throw (Excepcion&) {}

void FileService::Finalizar() throw (Excepcion&) {}

void FileService::mensajeEntranteManejador(IMensajero* pMensajero, const string & pMensaje) {
    try
    {
        // <token>|<usuario>|<FILE_SERVICE>|<Metodo>|<args>
        
        hidratarMensaje(pMensaje);
        validarIntegridadMensaje();
        
        if (getNombre() != getMensaje()[2]) return;
        
        // Se determina a que accion corresponde el mensaje.
        string metodo = getMensaje()[3];

        if (metodo == FileServiceConstants::DECLARAR_ARCHIVO)
        {
            this->declareFile(pMensajero, getMensaje());
        }
        else if (metodo == FileServiceConstants::BORRAR_ARCHIVO)
        {
            this->deleteFile(pMensajero, getMensaje());
        }
        else if (metodo == FileServiceConstants::PUT_CHUNK)
        {
            this->putFileChunk(pMensajero, getMensaje());
        }
        else if (metodo == FileServiceConstants::DEFINIR_ARCHIVO)
        {
            this->defineFile(pMensajero, getMensaje());
        }
        else if (metodo == FileServiceConstants::GET_REPOSITORIO)
        {
            this->getRepositorio(pMensajero, getMensaje());
        }
        else if (metodo == FileServiceConstants::GET_CHUNK)
        {
            this->getChunk (pMensajero, getMensaje());
        }
    }
    catch (int &pEx)
    {
        cerr << "El mensaje no se encuentra definido correctamente. " << pMensaje << endl;
    }
}

void FileService::responderEco(IMensajero* pMensajero, vector<string> &pComando) {
    pMensajero->Responder(FileServiceConstants::OK);
}

void FileService::declareFile(IMensajero* pMensajero, vector<string>& pComando) {
    
    /* Formato del comando:
     * 
     *  <token>(0)|<usuario>(1)|<FILE_SERVICE>(2)|<DECLARE_FILE>(3)|<file_name>(4)|<version_archivo>(5)
     * 
     * Formato de respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * <resultado> = OK    => <args> = <nombre_archivo>
     * <resultado> = ERROR => <args> = <codigo_error>;<args_sec>
     */
    string fileName = Codec::decodificar(pComando[4]);
    
    try
    {
        bool archivoExistente = NULL != this->GetRepositorio(pComando[1])->GetRepoItemByFilename(fileName);

        if (archivoExistente)
        {
            /* Enviar respuesta error al cliente.
             * 
             * <args_sec> = <nombre_archivo>
             */ 

            string respuesta = pComando[0]; // Token
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::NOMBRE_SERVICIO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::DECLARAR_ARCHIVO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR_ARCHIVO_EXISTENTE;
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];      // Nombre de archivo.

            pMensajero->Responder(respuesta);        
            return;
        }

        // Creamos el archivo temporal.

        string fullPath = this->GetRepositorio(pComando[1])->GetPath();
        fullPath += SEPARADOR_NODO;
        fullPath += fileName;
        fullPath += EXTENSION_ARCHIVO_DECLARACION;

        std::ofstream newFile(fullPath.c_str(), std::ios_base::binary | ios_base::out);

        if (!newFile.is_open()) {

            cerr << "No se puede crear el archivo " << fullPath << endl;

            this->notificarErrorEjecucion (pMensajero
                                         , pComando
                                         , FileServiceConstants::DECLARAR_ARCHIVO
                                         , FileServiceConstants::ERROR_INTERNO_SERVIDOR
                                         , "Error interno del servidor.");
            return;
        }  
        else
        {
            newFile.close();
        }

        /* Instancio el item correspondiente al archivo en el repositorio.
         */
        RepoItem* item = GetRepositorio(pComando[1])->CrearItem();

        item->SetEstado(ARCHIVO_DECLARADO);
        item->SetFilename(fileName);
        item->SetVersion(NumeroUtils::ParseUInt32(pComando[5]));

        this->GetRepositorio(pComando[1])->AgregarRepoItem(item);

        /* Comunico la respuesta al cliente
         * 
         * Formato de la respuesta:
         * 
         * <token>;<recurso>;<accion>;<resultado>;<args>
         * 
         * OK => <args> = <nombre_archivo>
         * ERROR => <args> = <mensaje_error>
         */

        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::DECLARAR_ARCHIVO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::OK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[4];

        pMensajero->Responder(respuesta);
        
    }
    catch (Excepcion &ex)
    {
        cerr << "Error en FileService.DeclareFile: " << ex.what() << endl;
    }
}

void FileService::deleteFile(IMensajero* pMensajero, vector<string>& pComando) {
    
    /* Formato del comando:
     * 
     *  <token>(0)|<usuario>(1)|<recurso>(2)|<accion>(3)|<file_name>(4)|<version_archivo>(5)
     * 
     * Formato de respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * <resultado> = OK    => <args> = <nombre_archivo>
     * <resultado> = ERROR => <args> = <codigo_error>;<args_sec>
     */
    string fileName = Codec::decodificar(pComando[4]);
    
    try
    {
        RepoItem* item = this->GetRepositorio(pComando[1])->GetRepoItemByFilename(fileName);
        
        bool archivoInExistente = NULL == item;

        if (archivoInExistente)
        {
            /* Enviar respuesta error al cliente.
             * 
             * <args_sec> = <nombre_archivo>
             */ 

            string respuesta = pComando[0]; // Token
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::NOMBRE_SERVICIO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::BORRAR_ARCHIVO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR_ARCHIVO_INEXISTENTE;
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];      // Nombre de archivo.

            pMensajero->Responder(respuesta);        
            return;
        }

        _fileUtils.EliminarArchivo(item->GetRutaCompletaArchivo());
        item->SetEstado(ARCHIVO_BORRADO);

        /* Comunico la respuesta al cliente
         * 
         * Formato de la respuesta:
         * 
         * <token>;<recurso>;<accion>;<resultado>;<args>
         * 
         * OK => <args> = <nombre_archivo>
         * ERROR => <args> = <mensaje_error>
         */

        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::BORRAR_ARCHIVO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::OK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[4];

        pMensajero->Responder(respuesta);
    }
    catch (Excepcion &ex)
    {
        cerr << "Error en FileService.DeclareFile: " << ex.what() << endl;
    }
}

void FileService::putFileChunk(IMensajero* pMensajero, vector<string>& pComando) {
    
    /* Formato del comando: 
     * 
     * <token>(0);<nombre_usuario>(1);<recurso>(2);<accion>(3);<nombre_archivo>(4);<hash>(5);<numero_chunk>(6);<chunk>(7)
     */
    string fileName = Codec::decodificar(pComando[4]);
    
    try
    {
        // Se valida la existencia del archivo.
        
        RepoItem* item = this->GetRepositorio(pComando[1])->GetRepoItemByFilename(fileName);
        
        if (NULL == item)
        {
            // El archivo no fue declarado.  Se informa el error al cliente.
            
            string respuesta = pComando[0]; // Token
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::NOMBRE_SERVICIO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::PUT_CHUNK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += "Archivo no declarado.";
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];       // Nombre Archivo
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[6];       // Nro chunk
            
            pMensajero->Responder(respuesta);

            return;
        }
        
        /* Archivo declarado.  Se concatena el chunk al archivo y se actualiza
         * el repositorio.
         */
        
        string rutaArchivoDeclaracion = item->GetRutaCompletaArchivo() + EXTENSION_ARCHIVO_DECLARACION;
        std:ofstream outputFile;
        
        outputFile.open(rutaArchivoDeclaracion.c_str(), std::ios_base::binary | ios_base::out | ios::app);

        // Para nuevos archivos, los chunks se van guardando siempre al final
        // del archivo
        outputFile.seekp(0, ios_base::end);

        // Decodifico chunk, obtentiendo su tamaño.
        TP::Codec codec;
        uint32_t sizeData;
        uint8_t* data;
        codec.decodificar(data, sizeData, pComando[7]);
        outputFile.write((char*)data, sizeData);
        outputFile.close();
        
        item->AddHash(pComando[5]);
        item->SetEstado(ARCHIVO_CARGANDO);
                
        // Se notifica el resultado de la ejecucion al cliente.

        /* Formato de la respuesta:
         * 
         * <token>;<recurso>;<accion>;<resultado>;<args>
         * 
         * OK => <args> = <nombre_archivo>;<numero_chunk>
         * ERROR => <args> = <mensaje_error>
         */        

        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::PUT_CHUNK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::OK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[4]; // nombre archivo.
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[6];   // Nro chunk

        pMensajero->Responder(respuesta);
    }
    catch (Excepcion &ex)
    {
        cerr << "Fallo al cargar un chunk.  " << ex.what() << endl;
    }
}

void FileService::defineFile(IMensajero* pMensajero, vector<string>& pComando) {
    
    /* Formato del comando:
     * 
     * <token>(0);<nombre_usuario>(1);<recurso>(2);<accion>(3);<nombre_archivo>(4)
     * 
     * Formato de la respuesta:
     * 
     * <token>;<recurso>;<accion>;<resultado>;<args>
     * 
     * OK => <args> = <nombre_archivo>
     * ERROR => <args> = <mensaje_error>;<args_sec>
     * 
     */            
    string fileName = Codec::decodificar(pComando[4]);
    RepoItem* item = this->GetRepositorio(pComando[1])->GetRepoItemByFilename(fileName);
    
    if (NULL == item)
    {
        /* El archivo es inexistente.  Se notifica la situacion al cliente.
         * 
         * <args_sec> = <nombre_archivo>     
         */
        
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::PUT_CHUNK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::ERROR;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::ERROR_ARCHIVO_INEXISTENTE;
        respuesta += SEPARADOR_CAMPO;
        respuesta += "Archivo no declarado.";
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[4];       // Nombre Archivo

        pMensajero->Responder(respuesta);
        
        return;
    }
    
    string fileNameTemp = item->GetRutaCompletaArchivo() + EXTENSION_ARCHIVO_DECLARACION;
    
    if (0 == rename(fileNameTemp.c_str(), item->GetRutaCompletaArchivo().c_str())) {

        // Configuro en el repositorio el estado del archivo.
        
        item->SetEstado(ARCHIVO_CARGADO);
        
        // Envio de respuesta al cliente.
        
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::DEFINIR_ARCHIVO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::OK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pComando[4];       // Nombre de archivo.

        pMensajero->Responder(respuesta);
    } 
    else 
    {
        cerr << "No se puede crear el archivo " << item->GetRutaCompletaArchivo() << ".  Error IO:" << endl;
        this->notificarErrorEjecucion(pMensajero, pComando, FileServiceConstants::DEFINIR_ARCHIVO, FileServiceConstants::ERROR_INTERNO_SERVIDOR, "Error interno de servidor.");
    }
}

void FileService::getRepositorio (IMensajero* pMensajero, vector<string> &pComando){
    
    // Formato invocacion: <token>|<recurso>|<accion>

    // Formato respuesta: <token>|<recurso>|<accion>|<resultado>|<args>

    try
    {
        // Se serializa y codifica el respositorio para su transmision a través de 
        // la conexion.

        string repositorio = this->GetRepositorio(pComando[1])->Serializar();
        string repositorioSerializado = string("");
        _codec.codificar(repositorioSerializado, (uint8_t*) repositorio.c_str(), repositorio.length());

        // Se responde a la solicitud.

        string respuesta = pComando[0]; // Token

        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::GET_REPOSITORIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::OK;
        respuesta += SEPARADOR_CAMPO;
        respuesta += repositorioSerializado;

        pMensajero->Responder(respuesta);
        
    }
    catch (Excepcion &ex)
    {
        cerr << "Fallo en solicitud de respositorio. " << ex.what() << endl;
    }
}

Repositorio* FileService::GetRepositorio(const string &pNombreUsuario){
    return _admRepositorioCentral->GetRepositorioPorIdentificador(pNombreUsuario);
}

void FileService::getChunk (IMensajero* pMensajero, vector<string> &pComando){

    /* Formato de invocacion:
     * 
     * <token>(0);<nombre_usuario>(1);<recurso>(2);<accion>(3);<nombre_archivo>(4),<numero_chunk>(5)
     * 
     * Formato de respuesta:
     * 
     * <token>(0);<recurso>(1);<accion>(2);<resultado>(3);<args>
     * 
     */
    string fileName = Codec::decodificar(pComando[4]);
    try
    {
        RepoItem* item = this->GetRepositorio(pComando[1])->GetRepoItemByFilename(fileName);

        if (NULL == item)
        {
            // Archivo inexistente. Comunico el error al cliente.
            
            // <args> = <codigo_error>;<nombre_archivo>;<numero_chunk>
            
            string respuesta = pComando[0]; // Token
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::NOMBRE_SERVICIO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::GET_CHUNK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR_ARCHIVO_INEXISTENTE;
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];       // Nombre Archivo
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[5];       // Nro chunk

            pMensajero->Responder(respuesta);        

            return;
        }
        
        if (NumeroUtils::ParseInt32(pComando[5]) >= item->GetCantidadHashes())
        {
            // El indice de chunk excede el numero de chunks que componen el 
            // archivo.  Se informa la situacion con un indice -1.
            
            // <args> = <nombre_archivo>;<numero_chunk>
            
            string respuesta = pComando[0]; // Token
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::NOMBRE_SERVICIO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::GET_CHUNK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::OK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];       // Nombre Archivo
            respuesta += SEPARADOR_CAMPO;
            respuesta += NumeroUtils::Int32AString(-1);  // Indica chunk indefinido.

            pMensajero->Responder(respuesta);        

            return;
        }
        
        /* El archivo existe y el hash solicitado corresponde con el archivo
         * indicado.  se comunica el chunk.
         * 
         * Formato de la respuesta:
         * 
         * <token>;<usuario>;<recurso>;<accion>;<resultado>;
         *      <nombre_archivo>
         * ;    <numero_chunk>
         * ;    <hash>
         * ;    <chunk>
         * 
         */
        uint8_t* chunk = NULL;
        size_t chunkLongitud = 0;
        uint32_t numeroChunk = NumeroUtils::ParseUInt32(pComando[5]);
        _fileUtils.ComputarChunk(chunk, chunkLongitud, item->GetRutaCompletaArchivo(), numeroChunk);

        if (chunkLongitud > 0 )
        {
            // <args> = <nombre_archivo>;<numero_chunk>;<hash>;<chunk>
            
            string chunkStr = "";
            _codec.codificar(chunkStr, chunk, chunkLongitud);
            
            string respuesta = pComando[0];     // token
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[1];           // usuario
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::GET_CHUNK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::OK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];           // nombre archvo
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[5];           // numero chunk
            respuesta += SEPARADOR_CAMPO;
            respuesta += item->GetHash(numeroChunk);
            respuesta += SEPARADOR_CAMPO;
            respuesta += chunkStr;
            
            pMensajero->Responder(respuesta); 
        }
        else
        {
            // Archivo inexistente. Comunico el error al cliente.
            
            // <args> = <codigo_error>;<nombre_archivo>;<numero_chunk>
            
            string respuesta = pComando[0]; // Token
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::NOMBRE_SERVICIO;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::GET_CHUNK;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += FileServiceConstants::ERROR_INTERNO_SERVIDOR;
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[4];       // Nombre Archivo
            respuesta += SEPARADOR_CAMPO;
            respuesta += pComando[5];       // Nro chunk

            pMensajero->Responder(respuesta);        
            
            cerr << "No se puede computar el chunk " << pComando[5] << " del archivo " << item->GetFilename() << "." << endl;
        }
    }
    catch (Excepcion &ex)
    {
        cerr << "Fallo al ejecutar la accion Get Chunk. " << ex.what() << endl;
    }
}

void FileService::notificarErrorEjecucion (IMensajero* pMensajero, vector<string> &pComando, const string &pAccion,  const string &pCodigoError, const string &pError){
    try
    {
        // Enviar respuesta error al cliente
        string respuesta = pComando[0]; // Token
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::NOMBRE_SERVICIO;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pAccion;
        respuesta += SEPARADOR_CAMPO;
        respuesta += FileServiceConstants::ERROR;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pCodigoError;
        respuesta += SEPARADOR_CAMPO;
        respuesta += pError;

        pMensajero->Responder(respuesta);
    }
    catch (Excepcion &ex)
    {
        cerr << "Fallo al notificar error de ejecucion al cliente. " << ex.what() << endl;
    }
}